package com.hmall.search.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.hmall.api.dto.ItemDTO;
import com.hmall.common.domain.PageDTO;
import com.hmall.search.domain.dto.FilterResultDto;
import com.hmall.search.domain.po.ItemPageQuery;
import com.hmall.search.service.SearchService;
import lombok.RequiredArgsConstructor;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@Service
@RequiredArgsConstructor
public class SearchServiceImpl implements SearchService {

    private final RestHighLevelClient client;

    public BoolQueryBuilder boolQueryForItems(ItemPageQuery query){

        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        if(StrUtil.isNotBlank(query.getKey())){
            boolQuery.must(QueryBuilders.matchQuery("name",query.getKey()));
        }
        if(StrUtil.isNotBlank(query.getCategory())){
            boolQuery.filter(QueryBuilders.termQuery("category",query.getCategory()));
        }
        if(StrUtil.isNotBlank(query.getBrand())){
            boolQuery.filter(QueryBuilders.termQuery("brand",query.getBrand()));
        }
        if(query.getMaxPrice()!=null ){
            boolQuery.filter(QueryBuilders.rangeQuery("price").lte(query.getMaxPrice()));
        }
        if(query.getMinPrice()!=null ){
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(query.getMinPrice()));
        }
        return boolQuery;
    }

    @Override
    public PageDTO<ItemDTO> search(ItemPageQuery query) throws IOException {
        SearchRequest request = new SearchRequest("items");
        BoolQueryBuilder boolQuery = boolQueryForItems(query);
        FunctionScoreQueryBuilder functionQuery = QueryBuilders.functionScoreQuery(boolQuery, new FunctionScoreQueryBuilder.FilterFunctionBuilder[]
                {
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(QueryBuilders.termQuery("isAD", true),
                                ScoreFunctionBuilders.weightFactorFunction(100))
                }).boostMode(CombineFunction.MULTIPLY);

        int from = (query.getPageNo() - 1) * query.getPageSize();

        request.source().query(functionQuery);
        request.source().from(from).size(query.getPageSize());
        if(StrUtil.isNotBlank(query.getSortBy())) {
            request.source().sort(query.getSortBy(), SortOrder.DESC);
        }
        //高亮
        request.source().highlighter(SearchSourceBuilder.highlight().field("name"));
//        }else {
//            request.source().sort("updateTime",SortOrder.DESC);
//        }
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        SearchHit[] searchHits = response.getHits().getHits();
        PageDTO<ItemDTO> pageRresult=new PageDTO<>();
        long num = response.getHits().getTotalHits().value;
        pageRresult.setTotal(num);
//        pageRresult.setPages(num%query.getPageSize()==0?num/query.getPageSize():num/query.getPageSize()+1);
        pageRresult.setPages((long)query.getPageSize());
        List<ItemDTO> itemDTOList=new ArrayList<>();
        for (SearchHit searchHit : searchHits) {
            String json = searchHit.getSourceAsString();
            ItemDTO itemDTO = JSONUtil.toBean(json, ItemDTO.class);
            Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
            if(highlightFields!=null && highlightFields.size()>0){
                HighlightField field = highlightFields.get("name");
                String hightLightName = field.getFragments()[0].toString();
                itemDTO.setName(hightLightName);
            }

            itemDTOList.add(itemDTO);
        }
        pageRresult.setList(itemDTOList);

        return pageRresult;
    }

    @Override
    public FilterResultDto filter(ItemPageQuery query) throws IOException {
        SearchRequest request = new SearchRequest("items");

        BoolQueryBuilder boolQuery = boolQueryForItems(query);

        request.source().query(boolQuery);
        request.source().size(0);
        request.source().aggregation(AggregationBuilders.terms("category_agg").field("category").size(10));
        request.source().aggregation(AggregationBuilders.terms("brand_agg").field("brand").size(10));
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        Terms categoryAgg = response.getAggregations().get("category_agg");
        Terms brandAgg = response.getAggregations().get("brand_agg");
        List<? extends Terms.Bucket> categoryAggBuckets = categoryAgg.getBuckets();
        List<? extends Terms.Bucket> brandAggBuckets = brandAgg.getBuckets();


        List<String> category=new ArrayList<>();
        List<String> brand=new ArrayList<>();
        for (Terms.Bucket categoryAggBucket : categoryAggBuckets) {
            String categoryName = categoryAggBucket.getKeyAsString();
            category.add(categoryName);
        }
        for (Terms.Bucket brandAggBucket : brandAggBuckets) {
            String brandName = brandAggBucket.getKeyAsString();
            brand.add(brandName);
        }
        return new FilterResultDto(category,brand);
    }
}
